package cn.dengta.webapp.trade.service;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Consumer;
import javax.annotation.*;

import cn.dengta.common.context.RequestContext;
import cn.dengta.common.model.*;
import cn.dengta.common.redis.RedisUtil;
import cn.dengta.common.util.Common;
import cn.dengta.common.util.SpringUtil;
import cn.dengta.common.web.Page;
import cn.dengta.context.model.Module;
import cn.dengta.context.model.*;
import cn.dengta.context.web.RequestContextImpl;
import cn.dengta.webapp.base.entity.BaseEntity;
import cn.dengta.webapp.trade.config.CustomAgentConfig;
import cn.dengta.webapp.trade.config.GoldTradeProperties;
import cn.dengta.webapp.trade.dao.UserCoinDao;
import cn.dengta.webapp.trade.dao.UserCoinLogDao;
import cn.dengta.webapp.trade.dto.*;
import cn.dengta.webapp.trade.entity.*;
import cn.dengta.webapp.trade.model.*;
import cn.dengta.webapp.user.cache.UserCaches;
import cn.dengta.webapp.user.cache.UserSummary;
import cn.dengta.webapp.user.dto.ActAprilIntimacyDTO;
import cn.dengta.webapp.user.entity.User;
import cn.dengta.webapp.user.entity.game.Game;
import cn.dengta.webapp.user.entity.task.TaskRule;
import cn.dengta.webapp.user.entity.userLevel.UserLevelRule;
import cn.dengta.webapp.user.model.Gender;
import cn.dengta.webapp.user.service.RemoteActivityService;
import cn.dengta.webapp.user.service.RemoteTaskAccountService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import me.codeplayer.util.*;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.ibatis.cursor.Cursor;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import static cn.dengta.webapp.base.entity.BaseEntity.ID;
import static cn.dengta.webapp.trade.entity.UserCoin.ACTIVE_AMOUNT;
import static cn.dengta.webapp.trade.entity.UserCoin.USER_ID;

/**
 * 用户钻石 服务实现类
 *
 * @since 2022-03-11
 */
@Service
@Slf4j
public class UserCoinService extends AbstractTradeAccountService<UserCoin, UserCoinDao> {

	@Resource
	RedisTemplate<String, Object> redisTemplate;
	@Resource
	UserCoinLogDao userCoinLogDao;
	@Resource
	UserIntegralService userIntegralService;
	@Resource
	UserGoldService userGoldService;
	@Resource
	GoldTradeProperties goldTradeProperties;
	@Resource
	UserGoldLogService userGoldLogService;
	@Resource
	UserLevelService userLevelService;
	@Resource
	UserStatService userStatService;
	@Resource
	UserCaches userCaches;
	@Resource
	StringRedisTemplate stringRedisTemplate;
	@DubboReference
	RemoteActivityService remoteActivityService;

	@DubboReference
	RemoteTaskAccountService remoteTaskAccountService;
	@Resource
	UserIntimacyService userIntimacyService;

	/**
	 * 断言交易成功，否则抛出异常
	 */
	public static void assertTradeOK(@Nullable Messager<Long> msger) {
		if (!Messager.isOK(msger)) {
			throw new ErrorMessageException(msger);
		}
	}

	/**
	 * 断言交易成功，否则抛出异常
	 */
	public void assertTradeOK(Long userId, @Nullable Messager<?> msger) {
		if (!Messager.isOK(msger)) {
			UserCoin coin = this.get(userId);
			throw new ErrorMessageException(coin.responseForNoBalance());
		}
	}

	/**
	 * 校正余额不足的状态
	 */
	@Nullable
	public static <T> Messager<T> adjust(@Nullable Messager<T> msger, boolean vipRequired) {
		if (!Messager.isOK(msger)) {
			if (vipRequired) {
				msger.setStatus(MessagerStatus.NO_VIP);
			}
		}
		return msger;
	}

	/**
	 * 分页查询用户钻石
	 */
	public Page<UserCoinDTO> findPage(Page<UserCoin> page, Integer tab, User input) {
		QueryWrapper<UserCoin> wrapper = new QueryWrapper<>();
		final String prefix = "log.";
		if (tab != null) {
			switch (tab) {
				case 0: // 可用钻石 = 0
					wrapper.eq(prefix + ACTIVE_AMOUNT, 0);
					break;
				case 1: // 可用钻石 > 0
					wrapper.gt(prefix + ACTIVE_AMOUNT, 0);
					break;
				case 2: // 存在赠送钻石
					wrapper.gt(prefix + UserCoin.FREE_PART, 0);
					break;
			}
		}
		if (input != null) {
			wrapper.eq(input.getId() != null, prefix + USER_ID, input.getId());
			wrapper.like(StringUtil.notEmpty(input.getEmail()), "u.email", input.getEmail());
			wrapper.like(StringUtil.notEmpty(input.getNickname()), "u.nickname", input.getNickname());
			wrapper.eq(input.getAgentId() != null, "u." + User.AGENT_ID, input.getAgentId());
		}
		wrapper.orderByDesc(prefix + ID);
		return baseDao.findPage(page, wrapper);
	}

	/**
	 * 为指定用户创建 钻石 与 积分 账户
	 */
	public void createTradeAccount(Long userId) {
		UserCoin uc = initAccount(userId);
		save(uc);
		userIntegralService.createAccount(userId);
		userStatService.initForUser(userId);
	}

	/** 游戏消耗钻石 */
	public Messager<UserCoinTradeDTO> gameConsumeCoin(Long userId, Long gameId, Integer amount) {
		Game game = new Game();
		game.setId(gameId);
		final RequestContext req = RequestContext.get();
		CoinTradeDTO dto = CoinTradeDTO.of(userId, TradeType.COIN_FOR_GAME, amount, game, req);
		dto.setMode(CoinTradeDTO.MODE_FAST_RETURN);
		return tradeCoin(dto);
	}

	/**
	 * 处理钻石交易行为，并进行相应的日志记录
	 *
	 * @return 如果交易成功，将返回 null 或 status = "OK" 的消息实体。
	 */
	@Nullable
	public Messager<Long> trade(CoinTradeDTO dto) {
		final TradeType tradeType = dto.getTradeType();
		Messager<UserCoinTradeDTO> tradeCoin = tradeCoin(dto);
		if (!tradeCoin.isOK()) {
			return Messager.status(tradeCoin.getStatus());
		}
		return tradeType.isHandleToConfirm() ? Messager.hideData(tradeCoin.data().getLog().getId()) : null;
	}

	/**
	 * 处理钻石交易行为，并进行相应的日志记录
	 *
	 * @return 如果交易成功，将返回 null 或 status = "OK" 的消息实体。
	 */
	@Nullable
	public Messager<UserCoinLog> tradeLog(CoinTradeDTO dto) {
		final TradeType tradeType = dto.getTradeType();
		Messager<UserCoinTradeDTO> tradeCoin = tradeCoin(dto);
		if (!tradeCoin.isOK()) {
			return Messager.status(tradeCoin.getStatus());
		}
		return tradeType.isHandleToConfirm() || dto.isHandleToConfirm() ? Messager.hideData(tradeCoin.data().getLog()) : null;
	}

	/** 返回交易中的数据，便于其他业务计算 */
	public Messager<UserCoinTradeDTO> tradeCoin(CoinTradeDTO dto) {
		UserCoinTradeDTO tradeDto = new UserCoinTradeDTO();
		final TradeType tradeType = dto.getTradeType();
		// 避免传参错误
		Assert.isTrue(tradeType.isForCoinOrIntegral());
		final Long userId = dto.getFromUserId();
		final String freezeBizId = dto.getFreezeBizId();

		// 防止并发请求
		BaseEntity assocObj = dto.getAssocObj();

		UserCoin account = dto.getUserCoinForUpdate();
		if (account == null) {
			account = getByUserIdForUpdate(userId);
		}
		tradeDto.setAccount(account);
		Integer tradeAmount = dto.getTradeAmount();

		final int mode = dto.getMode();
		String remark = StringUtil.toString(dto.getRemark());

		if (StringUtil.notEmpty(freezeBizId) || (mode != CoinTradeDTO.MODE_ANY && tradeType.getFrom().isConsume())) {
			final Integer realActiveAmount = getRealActiveAmount(userId, account.getActiveAmount(), freezeBizId);
			if (realActiveAmount < tradeAmount) { // 余额不足
				if (StringUtil.notEmpty(freezeBizId)) {
					log.warn("进行冻结交易时，发现可用余额不足：用户={}，冻结交易号={}，可用余额={}，交易金额={}", userId, freezeBizId, realActiveAmount, tradeAmount);
					tradeAmount = Math.max(realActiveAmount, 0);
				} else {
					final Messager<Long> msger = account.responseForNoBalance();
					if (mode == CoinTradeDTO.MODE_FAST_FAIL) {
						UserCoinService.assertTradeOK(msger);
					}
					return Messager.status(msger.getStatus());
				}
			}
		}

		final Long toUserId = dto.getToUserId();

		final String ip = dto.getIp();
		final Date now = dto.getNow();
		// 是否是直播
		boolean isLive = dto.getLiveId() != null;
		// 发起方交易日志
		UserCoinLog log = UserCoinLog.trade(tradeType, true, account, tradeAmount, dto.getAddFreePart());

		log.setPlatformBonus(dto.getPlatformBonus());
		log.setPlatformFreeBonus(dto.getPlatformFreeBonus());
		log.handleAssocObject(assocObj);
		log.setAiId(dto.getAiId());
		log.setUserId(userId)
				.setLiveId(dto.getLiveId())
				.setToUserId(toUserId)
				.setAddIp(ip)
				.setRemark(remark)
				.setOrderNo("")
				.setHandleStatus((tradeType.isHandleToConfirm() || dto.isHandleToConfirm()) ? HandleStatus.INIT.getValue() : HandleStatus.DONE.getValue())
				.initTime(now);
		userCoinLogDao.insert(log);

		tradeDto.setLog(log);
		tradeDto.setIncomeUserId(toUserId);

		if (assocObj instanceof UseCoinFreePartAware) {
			((UseCoinFreePartAware) assocObj).useFreePart(log.getTradeFreePart());
		}

		boolean isRecharge = tradeType == TradeType.COIN_RECHARGE;
		if (isRecharge) {
			account.addFlag(UserCoin.BIZ_FLAG_RECHARGED);
			BigDecimal rechargeAmount = dto.getRechargeAmount();
			if (rechargeAmount != null) {
				account.setTotalRecharge(account.getTotalRecharge().add(rechargeAmount));
			}
			SpringUtil.asyncRun(() -> userLevelService.change(UserLevelRule.UserLevelRuleType.RECHARGE_COIN, null, assocObj, Bean.of(User::new, userId), log.getTradeAmount(), now), "用户等级（充值钻石）");
		}
		account.setUpdateTime(now);
		update(account);

		// 判断 消费方是否系统黑名单  是：对方不增加收益
		final UserSummary userSummary = userCaches.get(userId);

		BigDecimal integral = null;
		if (toUserId != null && tradeType.getTo() != null && dto.getIncomeMode() >= 0 && !userSummary.hasBlack()) {
			log.setAiUserIncomeRate(dto.getIncomeRatio() != null ? BigDecimal.valueOf(dto.getIncomeRatio()).movePointLeft(2) : null);
			UserIntegralLog integralLog = userIntegralService.addIntegralIncome(toUserId, log, assocObj, tradeType, null);
			if (integralLog != null) {
				integral = integralLog.getTradeAmount().abs();
			}
		} else if (isRecharge) {
			userIntegralService.addInviteRechargeIncome(log, assocObj);
		}
		// 直播不增加亲密度
		if (!isLive && !dto.isHandleToConfirm()) { // 处理钻石才会增加亲密度
			addIntimacy(log.getFlow(), userId, tradeAmount, toUserId);
			addActAprilIntimacy(log.getFlow(), userId, toUserId, tradeAmount, log.getTradeFreePart());
		}

		if (freezeBizId != null || tradeType.getFrom().isConsume()) {
			final HashOperations<String, String, Long> redisHash = redisTemplate.opsForHash();
			final Object[] toDelete = freezeBizId == null ? new String[] { RedisKey.USER_ACTIVE_COIN_HASH_KEY }
					: new String[] { freezeBizId, RedisKey.USER_ACTIVE_COIN_HASH_KEY };
			redisHash.delete(RedisKey.userFrozenCoinHashKey(userId), toDelete);
		}
		if (Flow.CONSUME.eq(log.getFlow()) && !TradeType.COIN_FOR_EXCHANGE_GOLD.eq(log.getTradeType()) && Math.abs(tradeAmount) >= 100 && Bean.hasId(assocObj)) { // 每次消耗钻石超过100增加经验值
			SpringUtil.asyncRun(() -> userLevelService.change(UserLevelRule.UserLevelRuleType.USE_COIN, null, assocObj, Bean.of(User::new, userId), Math.abs(log.getTradeAmount()), now), "用户等级（消耗钻石）");
		}
		if (toUserId != null) {
			UserStatChangeDTO changeDTO = new UserStatChangeDTO().init(userId, tradeType, toUserId, userId, now, null, integral);
			switch (tradeType) {
				case COIN_FOR_AUDIO:
				case COIN_FOR_VIDEO:
					changeDTO.setAmount(tradeAmount);
					Assert.notNull(dto.getEquivalentAmount());
					changeDTO.setCallTime(dto.getEquivalentAmount());
					userStatService.sync(changeDTO);
					break;
				case COIN_RECHARGE:
					changeDTO.setAmount(dto.getEquivalentAmount());
					userStatService.sync(changeDTO);
					break;
				case COIN_FOR_MSG:
				case COIN_FOR_GIFT:
				case COIN_FOR_GIFT_LIVE:
					changeDTO.setAmount(tradeAmount);
					userStatService.sync(changeDTO);
					break;
			}
		}

		final CustomAgentConfig cfg = CustomAgentConfig.get();
		if (!User.APP_VERIFY_USER_IDS.contains(userId)) {
			if (userSummary != null && account.getAllCoinRecharge().compareTo(cfg.getMinRecharge()) >= 0) {
				stringRedisTemplate.opsForZSet().add(CustomAgentConfig.recRechargeUserKey, userId.toString(), now.getTime());
			}
		}
		return Messager.exposeData(tradeDto);
	}

	public UserIntegralLog onlyIntegral(CoinTradeDTO dto) {
		final TradeType tradeType = dto.getTradeType();
		// 避免传参错误
		Assert.isTrue(tradeType.isForCoinOrIntegral());
		UserCoin account = dto.getUserCoinForUpdate();
		final Long userId = dto.getFromUserId();
		if (account == null) {
			account = getByUserIdForUpdate(userId);
		}
		// 防止并发请求
		BaseEntity assocObj = dto.getAssocObj();
		final Integer tradeAmount = dto.getTradeAmount();
		UserCoinLog log = UserCoinLog.trade(tradeType, true, account, tradeAmount, dto.getAddFreePart());
		log.setAiId(dto.getAiId());
		log.setPlatformBonus(dto.getPlatformBonus());
		log.handleAssocObject(assocObj);
		final Long toUserId = dto.getToUserId();
		log.setUserId(userId)
				.setLiveId(dto.getLiveId())
				.setToUserId(toUserId)
				.setAddIp(dto.getIp())
				.setOrderNo("")
				.setHandleStatus(tradeType.isHandleToConfirm() ? HandleStatus.INIT.getValue() : HandleStatus.DONE.getValue())
				.initTime(dto.getNow());
		UserIntegralLog integralLog = userIntegralService.addIntegralIncome(toUserId, log, assocObj, tradeType, null);
		addIntimacy(log.getFlow(), userId, tradeAmount, toUserId);
		addActAprilIntimacy(log.getFlow(), userId, toUserId, tradeAmount, log.getTradeFreePart());
		return integralLog;
	}

	/**
	 * 增加亲密度
	 */
	public void addIntimacy(Integer flow, Long userId, Integer tradeAmount, Long toUserId) {
		tradeAmount = Math.abs(tradeAmount);
		if (checkAddIntimacy(flow, tradeAmount)) {
			ZSetOperations<String, Object> opsForZSet = redisTemplate.opsForZSet();
			opsForZSet.incrementScore(RedisKey.INTIMACY_CONSUMER, userId.toString(), tradeAmount);
			if (Bean.isValidId(toUserId)) {
				final Pair<Long, Long> pair = Common.sort(userId, toUserId);
				Integer currentIntimacy = userIntimacyService.addUserScore(pair.getLeft(), pair.getRight(), tradeAmount);
				//				double currentIntimacy = NumberUtil.getDouble(opsForZSet.incrementScore(RedisKey.INTIMACY_COMMON, Common.sortKey(userId, toUserId, null), tradeAmount), 0D);
				//				Integer currentIntimacy = userIntimacyService.findScoreByUser(pair.getLeft(), pair.getRight());
				log.info("【intimacy】用户{} 接收用户={} 增加亲密度={} 总亲密度={}", userId, toUserId, tradeAmount, currentIntimacy);

				int oldIntimacy = currentIntimacy - tradeAmount;
				IntimacyLevel oldLevel = IntimacyLevel.calcLevel(NumberUtil.getInt(oldIntimacy));
				IntimacyLevel level = IntimacyLevel.calcLevel(NumberUtil.getInt(currentIntimacy));
				if (level.gt(oldLevel)) {
					remoteTaskAccountService.change(TaskRule.TaskRuleType.INTIMACY_LEVEL, null, IntimacyLevelEntity.of(oldLevel, level).withId(0L), Bean.of(User::new, userId), null, new Date());
				}
				// TODO 用户亲密度升级 推送用户
			}
		}
	}

	/** 增加4月活动亲密度 */
	public void addActAprilIntimacy(Integer flow, Long userId, Long toUserId, Integer tradeAmount, Integer tradeFreePart) {
		log.info("[4月活动亲密度]addActAprilIntimacy: flow={}, userId={}, toUserId={}, tradeAmount={}, tradeFreePart={}", flow, userId, toUserId, tradeAmount, tradeFreePart);
		if (checkAddIntimacy(flow, tradeAmount) && userId != null && toUserId != null) {
			final ActAprilIntimacyDTO actDto = new ActAprilIntimacyDTO();
			Map<Long, UserSummary> summaryMap = userCaches.find(userId, toUserId);
			if (summaryMap.get(userId).getGender() == Gender.FEMALE) {
				actDto.setUserId(userId);
				actDto.setToUserId(toUserId);
			} else if (summaryMap.get(toUserId).getGender() == Gender.FEMALE) {
				actDto.setUserId(toUserId);
				actDto.setToUserId(userId);
			} else {
				log.warn("【4月活动亲密度】userId={}, toUserId={}", userId, toUserId);
				return;
			}
			actDto.setActivityCoin(tradeFreePart = Math.abs(tradeFreePart));
			actDto.setRechargeCoin(Math.abs(tradeAmount) - tradeFreePart);
			SpringUtil.asyncRun(() -> remoteActivityService.saveOrUpdateIntimacy(actDto));
		}
	}

	private boolean checkAddIntimacy(Integer flow, Integer tradeAmount) {
		return Module.INTIMACY.isEnabled() && Flow.CONSUME.eq(flow) && tradeAmount > 0;
	}

	/**
	 * 查询指定用户的钻石账户，并检查余额是否达到 {@code minCoin}
	 *
	 * @param withUserCoin 是否在返回数据中附带 UserCoin 对象
	 */
	public Messager<UserCoin> checkBalance(Long userId, Integer minCoin, boolean withUserCoin) {
		UserCoin userCoin = getNearReal(userId);
		if (userCoin.hasNoEnough(minCoin)) {
			return userCoin.responseForNoBalance();
		}
		return Messager.hideData(withUserCoin ? userCoin : null);
	}

	/**
	 * 查询指定用户的钻石账户，并检查余额是否达到 {@code minCoin}
	 */
	public <T> Messager<T> checkBalance(Long userId, Integer minCoin) {
		return checkBalance(userId, minCoin, false).castDataType(null);
	}

	/**
	 * 临时冻结钻石（一般用于在通话过程中持续预冻结钻石，防止用户白嫖）
	 *
	 * @return 如果冻结成功，则返回最新的可用余额；如果失败，则返回 null
	 */
	@Nullable
	public Long freezeTemporary(FreezeCoinDTO dto) {
		final Long userId = dto.getUserId();
		// USER_FROZEN_COIN:HASH:36273
		final String redisKey = RedisKey.userFrozenCoinHashKey(userId);
		final String bizId = dto.getBizId();
		final long amount = dto.getAmount().longValue();
		final HashOperations<String, String, Long> redisHash = redisTemplate.opsForHash();

		final String lockKey = RedisKey.USER_FROZEN_COIN_LOCK_PREFIX + userId;
		final Map<String, Long> toUpdate = new HashMap<>(4);

		return RedisUtil.loadInLock(lockKey, () -> {
			// TODO 后续可以使用 lua 脚本去更好地保证数据一致性
			final Map<String, Long> map = redisHash.entries(redisKey);
			// @userActiveCoin
			final String userActiveCoinHashKey = RedisKey.USER_ACTIVE_COIN_HASH_KEY;
			Long activeAmount = null;
			Long currentFrozen = null;
			long totalFrozen = 0L;
			final Long zero = 0L;

			for (Map.Entry<String, Long> entry : map.entrySet()) {
				final String key = entry.getKey();
				final Long value = NumberUtil.getLong(entry.getValue(), zero);
				if (userActiveCoinHashKey.equals(key)) {
					activeAmount = value;
				} else if (key.equals(bizId)) {
					currentFrozen = value;
				} else {
					totalFrozen += value;
				}
			}
			if (currentFrozen == null || dto.isAllOrIncremental()) {
				currentFrozen = amount;
			} else {
				currentFrozen += amount;
			}

			totalFrozen += currentFrozen;

			if (activeAmount == null || activeAmount < totalFrozen) { // 如果缓存中没有余额数据，或者余额不足，就尝试获取最新数据
				activeAmount = getByUserId(userId).getActiveAmount().longValue();
				toUpdate.put(userActiveCoinHashKey, activeAmount);
			}
			final long remain = activeAmount - totalFrozen;
			final Long activeRemain;
			if (remain < 0) {
				activeRemain = null;
			} else {
				if (amount > 0) {
					toUpdate.put(bizId, currentFrozen);
				}
				activeRemain = remain;
			}
			redisHash.putAll(redisKey, toUpdate);

			return activeRemain;
		});

	}

	/**
	 * 获取【最接近】实际的用户钻石账户
	 *
	 * @param userId 用户ID
	 */
	public UserCoin getNearReal(@Nonnull Long userId) {
		final UserCoin uc = getByUserId(userId);
		final Integer activeAmount = uc.getActiveAmount();
		final Integer realActiveAmount = getRealActiveAmount(userId, activeAmount, null);
		final int diff = activeAmount - realActiveAmount;
		if (diff != 0) {
			uc.setActiveAmount(realActiveAmount);
			uc.setFrozenAmount(uc.getFrozenAmount() + diff);
		}
		return uc;
	}

	/**
	 * 获取最接近实际的用户可用金额
	 *
	 * @param userId 用户ID
	 * @param activeAmount 出自数据库的用户可用余额
	 * @param excludeBizId 需要排除计算的业务请求ID（一般是当前交易对应的冻结业务ID）
	 * @return 用户实际的可用余额
	 */
	public Integer getRealActiveAmount(@Nonnull Long userId, @Nonnull Integer activeAmount, @Nullable String excludeBizId) {
		if (activeAmount > 0) {
			final String redisKey = RedisKey.userFrozenCoinHashKey(userId);
			final HashOperations<String, String, Long> redisHash = redisTemplate.opsForHash();
			final Map<String, Long> map = redisHash.entries(redisKey);
			if (!map.isEmpty()) {
				int totalFrozen = 0;
				final Long zero = 0L;
				for (Map.Entry<String, Long> entry : map.entrySet()) {
					final String key = entry.getKey();
					final Long value = NumberUtil.getLong(entry.getValue(), zero);
					if (RedisKey.USER_ACTIVE_COIN_HASH_KEY.equals(key)) {
						if (activeAmount != value.intValue()) {
							log.warn("用户{} 的可用金额 缓存={} 与 数据库={} 不一致", userId, value, activeAmount);
						}
					} else if (excludeBizId == null || !excludeBizId.equals(key)) {
						totalFrozen += value;
					}
				}
				final int balance = activeAmount - totalFrozen;
				if (balance < 0) {
					log.warn("用户{} 冻结金额异常：可用金额={} 与 冻结金额={} 不一致", userId, activeAmount, totalFrozen);
				}
				return balance;
			}
		}
		return activeAmount;
	}

	@Override
	protected UserCoin initAccount(@Nonnull Long userId) {
		return new UserCoin()
				.init(userId, RequestContext.get().now());
	}

	public boolean updateAllCoinRecharge(Long id, BigDecimal recharge) {
		return baseDao.updateAllCoinRecharge(id, recharge) > 0;
	}

	/** 兑换金币 */
	public Messager<UserCoinTradeDTO> exchangeGold(Long userId, Integer golds) {
		Integer coins = goldTradeProperties.getCoins(golds);
		UserCoin userCoin = this.getByUserIdForUpdate(userId);
		if (Cmp.lt(userCoin.getActiveAmount(), coins)) {
			return Messager.status(MessagerStatus.LESS_COIN_GOLD);
		}
		UserGold gold = userGoldService.get(userId);
		if (gold == null) {
			gold = UserGold.initDefault(userId);
			userGoldService.save(gold);
		}
		Messager<UserGoldTradeDTO> trade = userGoldService.trade(userId, golds, GoldTradeType.GOLD_EXCHANGE_COINS, null, BigDecimal.valueOf(coins), "兑换金币");
		Assert.isTrue(trade.isOK());
		UserGoldTradeDTO data = trade.data();
		CoinTradeDTO dto = CoinTradeDTO.of(userId, TradeType.COIN_FOR_EXCHANGE_GOLD, coins, data.getLog(), RequestContextImpl.get());
		dto.setUserCoinForUpdate(userCoin);
		dto.setMode(CoinTradeDTO.MODE_FAST_FAIL);
		Messager<UserCoinTradeDTO> tradeCoin = tradeCoin(dto);
		data.getLog().handleAssocObject(tradeCoin.data().getLog());
		userGoldLogService.update(data.getLog());
		return tradeCoin;
	}

	/**
	 * 查询最近金币更新过的用户
	 */
	public void scanMinUpdateTimeCoin(Date minUpdateTime, Consumer<UserCoin> consumer) {
		Cursor<UserCoin> cursor = baseDao.scanMinUpdateTimeCoin(minUpdateTime);
		for (UserCoin userCoin : cursor) {
			consumer.accept(userCoin);
		}
	}

	/**
	 * 累计充值金额 >= ?
	 */
	public List<Long> findGeTotalRecharge(Integer amount) {
		return baseDao.findGeTotalRecharge(amount);
	}

	public List<Long> findIntervalTotalRecharge(Integer beginAmount, Integer endAmount) {
		return baseDao.findIntervalTotalRecharge(beginAmount, endAmount);
	}

}
